package com.github.mvcframework.v1.servlet;

import com.github.mvcframework.annotaion.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @program: springv1
 * @description:
 * @author: Pop
 * @create: 2021-06-14 22:12
 **/
public class PopDispatchServlet extends HttpServlet {

    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<String>();

    /**
     * key -> 类名首字母小写
     * Object 对应实体
     */
    private Map<String,Object> ioc = new HashMap<String,Object>();

    /**
     * url 映射
     */
    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 委派，当执行时，会使用url和真正的method方法进行关联
        try{
            doDispatch(req,resp);
        }catch (Exception e)
        {
            e.printStackTrace();
            resp.getWriter().write("500 Exception Detail");
        }
    }


    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");

        if(!this.handlerMapping.containsKey(url))
        {
            resp.getWriter().write("404 not found!");
        }
        Map<String,String[]> params = req.getParameterMap();



        Method method=handlerMapping.get(url);

        //获得形参列表
        Class<?> [] parameterTypes = method.getParameterTypes();
        Object[] param = new Object[parameterTypes.length];

        for (int i = 0; i <parameterTypes.length ; i++) {
            Class<?> clazz = parameterTypes[i];
            if(clazz == HttpServletRequest.class)
            {
                param[i] = req;
            }else if(clazz == HttpServletResponse.class)
            {
                param[i] = resp;
            }else if(clazz == String.class)
            {
                //从requestParam中取出值，赋值到内容中
                //通过运行时的状态去拿到你
                Annotation[] [] pa = method.getParameterAnnotations();
                for (int j = 0; j < pa.length ; j ++) {
                    for(Annotation a : pa[j]){
                        if(a instanceof RequestParam){
                            String paramName = ((RequestParam) a).value();
                            if(!"".equals(paramName.trim())){
                                String value = Arrays.toString(params.get(paramName))
                                        .replaceAll("\\[|\\]","")//去掉数组的转为为字符串后的[]前后缀
                                        .replaceAll("\\s+",",");//去掉空格
                                param[i] = value;
                            }
                        }
                    }
                }
            }
        }

        method.invoke(ioc.get(toLowerFirstCase(method.getDeclaringClass().getSimpleName())),
                param);
    }


    @Override
    public void init(ServletConfig config) throws ServletException {

        // 初始化的重头戏

        //1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2.扫描对应类下的包
        doScanner(properties.getProperty("scanPackage"));
        //3.初始化到IOC容器
        doInstance();

        //4.aop的步骤

        //5.自动注入
        doAutowired();
        //mvc
        doInitHandlerMapping();

        System.out.println("Spring 容器初始化完成");

    }

    /**
     * 将扫描到的类初始化到一个享元模式的容器中去。
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {
        // 由于配置的是包路径，这里只需要替换成文件路径就行
        URL url=this.getClass().getClassLoader().
                getResource("/"+scanPackage.replaceAll("\\.","/"));
        File file = new File(url.getFile());

        //当成是文件夹去扫描下的所有文件
        for(File f:file.listFiles())
        {
            if(f.isDirectory()){
                //需要递归再次查找
                doScanner(scanPackage+"."+f.getName());
            }
            else{
                //名字转换成类名，这个时候获得也许是文件夹，也可能是.class文件
                if(!f.getName().endsWith(".class")){
                    continue;
                }

                String className = scanPackage+"."+f.getName().replace(".class","");
                classNames.add(className);
            }


        }
    }


    private void doInitHandlerMapping() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(Controller.class))
            {
                continue;
            }
            String baseUrl = "";
            if(clazz.isAnnotationPresent(RequestMapping.class))
            {
                baseUrl+=clazz.getAnnotation(RequestMapping.class).value();
            }
            // 这里只获取公共的方法，spring也是这样做的
            Method[] methods = clazz.getMethods();
            for(Method method:methods)
            {
                if(!method.isAnnotationPresent(RequestMapping.class))
                {
                    continue;
                }
                RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                String url = (baseUrl+requestMapping.value()).replaceAll("/+","/");
                handlerMapping.put(url,method);

                System.out.println("Mapped : "+url+" + "+method);
            }
        }
    }

    private void doAutowired() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //DI步骤，取出所有的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for(Field field:fields)
            {
                // 不含有就不作为
                if(!field.isAnnotationPresent(Autowired.class)){continue;}
                Autowired autowired=field.getAnnotation(Autowired.class);

                //应该字段应该是个接口，不该是实现类
                if(!field.getType().isInterface()){continue;}

                String beanName = autowired.value().trim();
                if("".equals(beanName))
                {
                    //没有自定义的名字，就直接按照类型注入
                    beanName = field.getType().getName();
                }

                try {
                    field.setAccessible(true);
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 实例化步骤
     */
    private void doInstance() {
        if(classNames.isEmpty()){return;}
        try{

            for(String className:classNames)
            {
                Class<?> clazz = Class.forName(className);
                if(clazz.isInterface()){continue;}
                // 如果是接口，是无法new的
                // 没加特定的注解，无法解析
                if(clazz.isAnnotationPresent(Controller.class))
                {
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);
                }else if(clazz.isAnnotationPresent(Service.class))
                {
                    // 服务层有几种情况

                    //2.不同包下可能存在同名得地类
                    /*
                     * 对于原来的spring而言，他会报错，也就是实例bean出错，因为发现
                     * 两个重名的bean，这个时候我们需要设置别名
                     * */
                    //1.默认首字母小写

                    String beanName = clazz.getAnnotation(Service.class).value();
                    if("".equals(beanName.trim()))
                    {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }

                    //1.默认首字母小写
                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);


                    /*
                    * 3.如果是接口，就需要寻找他的实现类，不过当
                    * 实现类有多个的时候，只能抛出异常，如果只有一个，就可以直接
                    * 初始化。
                    * */
                    for(Class<?> i : clazz.getInterfaces())
                    {
                        if(ioc.containsKey(i.getName()))
                        {
                            throw new Exception(i.getName()+"已存在");
                        }
                        ioc.put(i.getName(),instance);
                    }
                }else{
                    continue;
                }



            }

        }catch (Exception e)
        {

        }
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] +=32;
        return new String(chars);
    }

    private void doLoadConfig(String contextConfigLocation) {
        InputStream inputStream=this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=inputStream)
            {
                try{
                    inputStream.close();
                }catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
}
